/*
 * Copyright (C) 2010-2011 Josef Hardi <josef.hardi@gmail.com>
 * Copyright (C) 2010 Piotr Zagawa <piotr.zagawa@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package id.duapuluhdua.dakidaki.data.common;

import java.util.ArrayList;
import java.util.Observable;

import android.database.Cursor;

import id.duapuluhdua.dakidaki.Command;
import id.duapuluhdua.dakidaki.UserAlert;

/**
 * Provides basic operations to create the table and get records.
 */
public abstract class DataTable extends Observable
{
  /** Constants */
  public static final String COL_ID = "_ID";

  /** Fields */
  protected Database mDatabase;
  protected String mTableName;
  protected DataColumn[] mVecTableDefinition;

  private boolean bTableExists = false;

  public static class column
  {
    public static final int ID = 0;
  }

  /**
   * Default constructor.
   *
   * @param database the database object.
   * @param tableName the table name.
   * @param vecTableDefinition the description of each table field.
   */
  public DataTable(Database database, String tableName, DataColumn[] vecTableDefinition)
  {
    mDatabase = database;
    mTableName = tableName;
    mVecTableDefinition = vecTableDefinition;
  }

  /**
   * Gets the table name.
   *
   * @return a string name.
   */
  public String getTableName()
  {
    return mTableName;
  }

  /**
   * Gets the name for at the i-th column.
   *
   * @param i the i-th column index.
   * @return a string name.
   */
  public String getColumnName(int i)
  {
    return mVecTableDefinition[i].name;
  }

  /**
   * Creates a new table.
   *
   * @return true if the table is already exist or successfully created, or
   * false otherwise in each case.
   */
  public boolean createTable()
  {
    if (mDatabase.tableExists(mTableName)) {
      bTableExists = true;
    }
    else {
      final String sql = getSqlTableDefinition();
      bTableExists = mDatabase.executeSql(sql);

      if (bTableExists) {
        mDatabase.getTableVersion().setVersion(mTableName, getTableVersion());
      }
    }

    return bTableExists;
  }

  /* Constructs the SQL statement for table creation based on the table
   * definition. */
  private String getSqlTableDefinition()
  {
    String sql = "CREATE TABLE " + mTableName + " (";
    for (int i = 0; i < mVecTableDefinition.length; i++) {
      final DataColumn field = mVecTableDefinition[i];
      sql += field.toString();
      if (i < (mVecTableDefinition.length - 1))
        sql += ", ";
    }
    sql += ")";
    return sql;
  }

  /**
   * Checks whether the table is already present or not.
   *
   * @return true if the table is already exist, or false otherwise.
   */
  public boolean checkExistence()
  {
    if (mDatabase.tableExists(mTableName)) {
      bTableExists = true;
    }
    return bTableExists;
  }

  /**
   * Inserts data values into a table.
   *
   * @param values data values.
   * @return row id.
   */
  public long dataInsert(DataValues values)
  {
    final long newRowId = mDatabase.insert(mTableName, values.getAll());
    return newRowId;
  }

  /**
   *
   * @param values
   * @param rowId
   * @return
   */
  public boolean dataUpdate(DataValues values, long rowId)
  {
    final String where = String.format("_ID = %d", rowId);
    final int rowsAffected = mDatabase.update(mTableName, values.getAll(), where);
    return (rowsAffected > 0);
  }

  /**
   *
   * @param rowId
   * @return
   */
  public boolean dataDelete(long rowId)
  {
    final String where = String.format("_ID = %d", rowId);
    final int rowsAffected = mDatabase.delete(mTableName, where);
    return (rowsAffected > 0);
  }

  /**
   *
   * @param where
   * @return
   */
  public boolean dataDelete(String where)
  {
    final int rowsAffected = mDatabase.delete(mTableName, where);
    return (rowsAffected > 0);
  }

  /**
   * @param where
   * @param orderBy
   * @return
   */
  public ArrayList<Long> getRowsIdList(String where, String orderBy)
  {
    ArrayList<Long> vecList = new ArrayList<Long>();

    final Cursor cr = getDataRecords(where, orderBy);
    if (cr != null) {
      while (!cr.isAfterLast()) {
        final Long lID = new Long(cr.getLong(column.ID));
        vecList.add(lID);
        cr.moveToNext();
      }
      cr.close();
    }
    return vecList;
  }

  public Cursor getDataRecords()
  {
    return getDataRecords(null, null);
  }

  public Cursor getDataRecords(String where)
  {
    return getDataRecords(where, null);
  }

  public Cursor getDataRecord(long rowId)
  {
    Cursor cr = null;
    if (exists()) {
      final String sql = String.format(
          "SELECT * FROM %s WHERE _ID = %d", mTableName, rowId);
      cr = mDatabase.query(sql);
      if (cr != null) {
        if (cr.getCount() == 0) {
          cr.close();
          cr = null;
        }
        else {
          cr.moveToFirst();
        }
      }
    }
    return cr;
  }

  public Cursor getDataRecords(String where, String orderBy)
  {
    String whereString = "";
    if (where != null) {
      whereString = "WHERE " + where;
    }
    String orderByString = "ORDER BY _ID";
    if (orderBy != null) {
      orderByString = "ORDER BY " + orderBy;
    }
    Cursor cr = null;
    if (exists()) {
      final String sql = String.format(
          "SELECT * FROM %s %s %s", mTableName, whereString, orderByString);
      cr = mDatabase.query(sql);
      if (cr != null) {
        if (cr.getCount() == 0) {
          cr.close();
          cr = null;
        }
        else {
          cr.moveToFirst();
        }
      }
    }
    return cr;
  }

  /**
   * Counts the total number of records with a filter.
   *
   * @param where
   *            A WHERE filter.
   * @return The total number of records.
   */
  public int getCount(String where)
  {
    String whereString = "";
    if (where != null) {
      whereString = "WHERE " + where;
    }
    int count = 0;
    if (exists()) {
      final String sql = String.format(
          "SELECT * FROM %s %s", mTableName, whereString);
      final Cursor cr = mDatabase.query(sql);
      if (cr != null) {
        count = cr.getCount();
        cr.close();
      }
    }
    return count;
  }

  /**
   * Counts the whole records.
   *
   * @return The total number.
   */
  public int getCount()
  {
    return getCount(null);
  }

  /**
   * Sends data operation to observers.
   *
   * @param rowId
   * @param operation
   */
  public void sendOperation(long rowId, int operation)
  {
    sendOperation(rowId, operation, Command.CMD_NONE, null);
  }

  /**
   * Sends data operation to observers with command and track name.
   *
   * @param rowId
   * @param operation
   * @param command
   * @param trackName
   */
  public void sendOperation(long rowId, int operation, int command, String trackName)
  {
    // Do not notify observers in middle of transaction
    if (mDatabase.isInTransaction()) {
      return;
    }

    DataTableOperation op = new DataTableOperation();
    op.rowId = rowId;
    op.operation = operation;
    op.command = command;
    op.trackName = trackName;

    setChanged();
    notifyObservers(op);
  }

  /**
   * Checks whether the table is already created or it exists in the database
   * already.
   *
   * @return Returns true if the table exists, or false otherwise.
   */
  public boolean exists()
  {
    return bTableExists;
  }

  /**
   * Checks for the correct values before update.
   *
   * @param values
   *          The data values object.
   * @return The validating result.
   */
  public abstract UserAlert.Result validateValues(DataValues values);

  /**
   * Gets the current version of table structure.
   *
   * @return a version number.
   */
  public abstract int getTableVersion();

  /**
   * Gets default values for insert.
   *
   * @param bForInsert
   * @return
   */
  public abstract DataValues getNewValues(boolean bForInsert);
}
